package calao.util;

import java.util.HashMap;
import java.util.Iterator;

/** Maps time periods to objects.
 * Warning: iterating over elements is not thread-safe, meaning that two iterators as 
 * returned by {@link #listOverlaps(TimePeriod)} cannot be used at the same time.
 */
public class TimePeriodMap<V>
{

	int nextIteratorId = 0;
	
	class ValueIterator implements Iterator<V> {
		final TimePeriod period;
		final int endHash;
		final int id = ++nextIteratorId;
		int hash;
		Entry<V> next;
		ValueIterator(TimePeriod period) {
			this.period = period;
			hash = computeStartHash(period)-1;
			endHash = computeEndHash(period);
			nextEntry(true);
		}
		public boolean hasNext() {
			return next!=null;
		}
		public V next() {
			Entry<V> result = next;
			nextEntry(false);
			return result.value;
		}
		void nextEntry(boolean first) {
			// precond: we're on previous valid next except on first call
			// postcond: we're on a new valid next except if end reached
			if (!first) next = next.next;
			for (;;) {
				while (next!=null) {
					if (next.ref.lastIteratorId!=id) {
						next.ref.lastIteratorId = id;
						if (next.period.overlaps(period)) return;
					}
					next = next.next;
				}
				if (hash<endHash) next = map.get(++hash);
				else break;
			}
		}
		public void remove() {
			throw new UnsupportedOperationException();
		}
	}

	static class ValueRef {
		int lastIteratorId;		
	}
	
	static class Entry<V> {
		final TimePeriod period;
		final V value;
		final ValueRef ref;
		Entry<V> next;
		Entry(TimePeriod period, V value, ValueRef ref) {
			this.period = period;
			this.value = value;
			this.ref = ref;
		}
	}

	HashMap<Integer,Entry<V>> map = new HashMap<Integer,Entry<V>>();
	int hashLength = TimePeriod.DAY;

	public void put(TimePeriod period, V value)
	{
		ValueRef ref = new ValueRef();
		for (int hash=computeStartHash(period); hash<=computeEndHash(period); hash++) {
			indexOn(hash, period, value, ref);
		}
	}

	public void indexOn(int hash, TimePeriod period, V value, ValueRef ref)
	{
		Entry<V> entry = new Entry<V>(period, value, ref);
		Entry<V> next = map.put(hash, entry);
		if (next!=null) entry.next = next;
	}

	public void setHashLength(int hashLength)
	{
		if (!map.isEmpty()) throw new RuntimeException("map is not empty");
		this.hashLength = hashLength; 
	}

	public Iterator<V> listOverlaps(TimePeriod period)
	{
		return new ValueIterator(period);
	}

	int computeStartHash(TimePeriod p) {
		return (int)(p.start/hashLength);
	}

	int computeEndHash(TimePeriod p) {
		return (int)((p.end-1)/hashLength);
	}

}
